/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package agen.seleccion;

import agen.util.Arreglos;
import agen.util.exception.MetodoInexistente;
import agen.util.exception.PorcentajesInconsistentes;
import java.util.Arrays;
import java.util.Random;

/**
 * En esta clase se agrupan losmetodo relacoinados con la selección.
 * @author juan
 */
public class Seleccion {
    /*definición de los métodos de selección soportados*/

    public static final int METODO_RULETA = 0;
    public static final int METODO_ELITISTA = 1;
    public static final int METODO_RANKING = 2;
    public static final int METODO_CONTROL_NUMERO_ESPERADO = 3;
    /*Obviamente la suma de los porcentajes debe ser 100*/
    private static MetodoSeleccionCombinado[] metodoElegido = {new MetodoSeleccionCombinado(METODO_ELITISTA, 20), new MetodoSeleccionCombinado(METODO_RULETA, 80)};
// <editor-fold defaultstate="collapsed" desc="Getters y Setters">

    public static MetodoSeleccionCombinado[] getMetodoElegido() {
        return metodoElegido;
    }

    public static void setMetodoElegido(MetodoSeleccionCombinado[] metodoElegido) {
        Seleccion.metodoElegido = metodoElegido;
    }
// </editor-fold>

    public static int seleccion(int offset, int porcentaje, byte[] poblacion, byte[] poblacionModificada, double[] aptitudes, MetodoSeleccionCombinado[] metodoElegido, int eltosXIndividuo, int[] aptitudesOrdenadas)
            throws PorcentajesInconsistentes, MetodoInexistente {
        if (metodoElegido != null) {
            Seleccion.metodoElegido = metodoElegido;
        }
		int sumPorcentaje = 0;
        for (MetodoSeleccionCombinado metComb : metodoElegido) {
            sumPorcentaje += metComb.porcentaje;
            if (!(/*metComb.metodo == METODO_CONTROL_NUMERO_ESPERADO ||*/
                    metComb.metodo == METODO_ELITISTA ||
                    metComb.metodo == METODO_RANKING ||
                    metComb.metodo == METODO_RULETA)) {
                throw new MetodoInexistente("Seleccion.seleccion: el metodo en" + " metodoElegido no está definido");
            }
        }
        if (sumPorcentaje != 100) {
            throw new PorcentajesInconsistentes("Seleccion.seleccion: los" + " porcentajes en metodoElegido no suman 100%");
        }
        return aplicarMetodos(offset, porcentaje, poblacion, poblacionModificada, aptitudes, eltosXIndividuo, aptitudesOrdenadas);
    }

    private static int aplicarMetodos(int offset, int porcentaje, byte[] poblacion, byte[] poblacionModificada, 
							double[] aptitudes, int eltosXIndividuo, int[] aptitudesOrdenadas) {
        int len = 0;
		double porcentajeProporcional;
        for (int i = 0; i < metodoElegido.length; i++) {
            offset += len;
			/*Dado que: (metodoElegido[i].porcentaje / 100.0) * (porcentaje / 100.0)
			= (metodoElegido[i].porcentaje * porcentaje) / 10000.0*/
			porcentajeProporcional = (metodoElegido[i].porcentaje * porcentaje) / 10000.0;
            len = (int) Math.round(porcentajeProporcional * aptitudes.length);
            /* si el redondeo hace que se pase el indice ajustamos en el ultimo
             * intrvalo
			 */
            if (offset + len > aptitudes.length) {
                len = aptitudes.length - offset;
            }
            switch (metodoElegido[i].metodo) {
                case METODO_ELITISTA:
                    elitista(poblacion, poblacionModificada, offset, len, aptitudes, eltosXIndividuo, aptitudesOrdenadas);
                    break;
                case METODO_CONTROL_NUMERO_ESPERADO:
                    break;
                case METODO_RANKING:
                    ranking(0.5, poblacion, poblacionModificada, offset, len, aptitudes, eltosXIndividuo, aptitudesOrdenadas);
                    break;
                case METODO_RULETA:
                    ruleta(poblacion, poblacionModificada, offset, len, aptitudes, eltosXIndividuo, aptitudesOrdenadas);
                    break;
            }
        }
		return offset + len;
    }

    /**
     * Se realiza con los inversos de las aptitudes, ya que mientras más cerca de
     * cero mejor la aptitud, y mas ranuras le corresponde al individuo.
     * Para evitar cocientes que den numeros muy altos al denominador le sumamos 1.
     * 1 / (1 + aptitud)
     * TODO: Verificar que no encontraremos AutovaloresMaximos negativos.
     */
    private static void ruleta(byte[] poblacion, byte[] poblacionModificada,
            int offset, int len, double[] aptitudes, int eltosXIndividuo,
            int[] aptitudesOrdenadas) {
        double[] ranuras = new double[aptitudes.length];
        double sumRanuras = 0;
        for (int i = aptitudes.length - 1; i >= 0; i--) {
            /*Notar: en ranuras[0] queda el sumRanuras más grande*/
            sumRanuras += 1 / (1 + aptitudes[i]);
            ranuras[i] = sumRanuras;
        }
        if (aptitudesOrdenadas != null) {
            ruletaOrdenada(poblacion, poblacionModificada, offset, len, sumRanuras, ranuras,
                    eltosXIndividuo, aptitudesOrdenadas);
        } else {
            ruletaDesordenada(poblacion, poblacionModificada, offset, len, sumRanuras, ranuras,
                    eltosXIndividuo);
        }

    }

    /**
     * Estando ya las ranuras definidas usamos números pseudoaleatorios para
     * elegir que individuos tienen copias. Asumiendo que el array de aptitudes
     * sí pasó por el metodo sort1
     * @param poblacion
     * @param offset
     * @param len
     * @param sumRanuras
     * @param ranuras
     * @param eltosXIndividuo
     * @param aptitudesOrdenadas
     */
    private static void ruletaOrdenada(byte[] poblacion, byte[] poblacionModificada, int offset, int len,
            double sumRanuras, double[] ranuras, int eltosXIndividuo,
            int[] aptitudesOrdenadas) {
        Random r1 = new Random();
        int j, indice, offsetPob = offset * eltosXIndividuo;
        double ranuraGanadora;
        for (int i = (len - 1) * eltosXIndividuo; i >= 0; i -= eltosXIndividuo) {
            ranuraGanadora = r1.nextDouble() * sumRanuras;
            j = ranuras.length;
            /*No puede haber Out of boundaries porque ranuraGanadora nunca es
            > sumRanuras, ahorramos j > 0*/
            while (ranuras[--j] <= ranuraGanadora) {
            }
            //en j esta el inidviduo que gano la copia.
            indice = aptitudesOrdenadas[j] * eltosXIndividuo;
            if (indice % eltosXIndividuo != 0) {
                System.err.println("ruletaOrdenada: esta mal sacado el indice!!!");
            }
            for (int k = eltosXIndividuo - 1; k >= 0; k--) {
                /*acá esta la diferencia con ruletaOrdenada*/
                poblacionModificada[offsetPob + i + k] = poblacion[indice + k];
            }
        }
    }

    /**
     * Estando ya las ranuras definidas usamos números pseudoaleatorios para
     * elegir que individuos tienen copias. Asumiendo que el array de aptitudes
     * nunca pasó por el metodo sort1
     * @param poblacion
     * @param offset
     * @param len
     * @param sumRanuras
     * @param ranuras
     * @param eltosXIndividuo
     */
    private static void ruletaDesordenada(byte[] poblacion, byte[] poblacionModificada, int offset, int len, double sumRanuras, double[] ranuras, int eltosXIndividuo) {
        Random r1 = new Random();
        int j, indice, offsetPob = offset * eltosXIndividuo;
        double ranuraGanadora;
        for (int i = (len - 1) * eltosXIndividuo; i >= 0; i -= eltosXIndividuo) {
            ranuraGanadora = r1.nextDouble() * sumRanuras;
            j = ranuras.length;
            /*No puede haber Out of boundaries porque ranuraGanadora nunca es
            > sumRanuras, ahorramos j > 0*/
            while (ranuras[--j] <= ranuraGanadora) {
            }
            //en j esta el inidviduo que gano la copia.
            indice = j * eltosXIndividuo;

            for (int k = eltosXIndividuo - 1; k >= 0; k--) {
                /*acá esta la diferencia con ruletaOrdenada*/
                poblacionModificada[offsetPob + i + k] = poblacion[indice + k];
            }
        }
    }

    /**
     * Recorre de mayor a menor los ultimos n individuos en el array de aptitudes
     * ordenadeas de menor a mayor. Esto es, los mejores n individuos y los agrega
     * secuencialmente desde offset hasta offset + len en el array
     * poblacionModificada.
     * @param poblacion
     * @param offset
     * @param len
     * @param aptitudes
     * @param eltosXIndividuo
     * @param aptitudesOrdenadas  Si es null, se ordena primero, sino, se asume
     * ordenadas las aptitudes.
     */
    private static void elitista(byte[] poblacion, byte[] poblacionModificada, int offset, int len, double[] aptitudes, int eltosXIndividuo, int[] aptitudesOrdenadas) {
        int indice;
        ordenarArray(aptitudes, aptitudesOrdenadas);
        int tope = (offset + len) * eltosXIndividuo;
        for (int i = offset * eltosXIndividuo, j = 0; i < tope; i += eltosXIndividuo, j++) {
            /*obtengo la posicion original de la mejor aptitud*/
            indice = aptitudesOrdenadas[j] * eltosXIndividuo;
            for (int k = eltosXIndividuo - 1; k >= 0; k--) {
                poblacionModificada[i + k] = poblacion[indice + k];
            }
        }
    }

    /**
     * Agrupa funcionalidad repetida, ordena las aptitudes si no lo estan.
     * Problema, cuano hago new con una referencia a un objeto que recibo como
     * parametro, cambia sólo la referencia de la variable local y no la que me mandan.
     * @param aptitudes
     * @param aptitudesOrdenadas
     */
    private static void ordenarArray(double[] aptitudes, int[] aptitudesOrdenadas) {
        if (aptitudesOrdenadas == null) {
            aptitudesOrdenadas = new int[aptitudes.length];
            Arreglos.sort1(aptitudes, aptitudesOrdenadas, 0, aptitudes.length);
        }
    }

    /**
     * utiliza la formula del metodo de seleccion por ranking:
     *      rMin + 2 (n-i)(1-rMin) / (n-1)
     * Para optener c copias de cada individuo segun su aptitud, hasta completar len
     * indiciduos.
     * @param rMin
     * @param poblacion
     * @param offset
     * @param len
     * @param aptitudes
     * @param eltosXIndividuo
     * @param aptitudesOrdenadas
     */
    private static void ranking(double rMin, byte[] poblacion, byte[] poblacionModificada, int offset,
            int len, double[] aptitudes, int eltosXIndividuo,
            int[] aptitudesOrdenadas) {
        int indice;
        int indiceEnPob = 0, c, individuosCopiados=0;
        ordenarArray(aptitudes, aptitudesOrdenadas);
        while (individuosCopiados < len) {
            /*Redondeo la parte decimal.*/
            c =(int) Math.round(rMin + (2 * (aptitudes.length - indiceEnPob)
                    * (1 - rMin) / (aptitudes.length - 1)));
            if (individuosCopiados + c >= len) {
                c = len - individuosCopiados - 1;
            }
            if (c == 0){
                c = 1;
            }
            indice = aptitudesOrdenadas[indiceEnPob] * eltosXIndividuo;
             for (int j = (c - 1)* eltosXIndividuo ; j >= 0; j -= eltosXIndividuo) {
                for (int k = eltosXIndividuo - 1; k >= 0; k--) {
                    poblacionModificada[offset + j + k] = poblacion[indice + k];
                }
                individuosCopiados++;
            }
            offset += c * eltosXIndividuo;
            indiceEnPob++;
        }
    }
}
